//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Base/Engine.Base.h"
#include "Graphics/GLshell/RenderState.h"
#include "Graphics/GLshell/VertexAttribs.h"

namespace Engine
{
namespace Graphics
{
	using namespace Base;

	
	class Shader;
	class ShaderProgram;



	//
	// OpenGL (ES) State Manager
	//

	class StateManager : public BaseObject
	{
	// types
	public:
		// Texture //
		struct BindedTexture
		{
			friend class StateManager;

		private:
			uint			_unit;

		public:
			gles::GLuint	id;
			gles::GLenum	target;
			///
			BindedTexture () : _unit(-1), id(0), target(gles::GL_TEXTURE_2D)
			{}

			explicit
			BindedTexture (const BindedTexture &other) : _unit(-1), id(other.id), target(other.target)
			{}

			explicit
			BindedTexture (gles::GLenum target) : _unit(-1), id(0), target(target)
			{}
			
			uint	Unit ()	const	{ return _unit; }
		};

	private:
		typedef static_array< BindedTexture *, GLConst::MAX_TEXTURE_UNITS >	texture_units_t;
		typedef static_stack< gles::GLuint, 16 >							buffer_stack_t;
		typedef static_stack< ivec4, 8 >									viewport_stack_t;


	// variables
	private:
		// Texture //
		texture_units_t		_textureUnits;
		uint				_maxTextureUnits;
		uint				_activeTextureUnit;

		// Vertex Attribs //
		bitfield32_t		_enabledVertexAtttribs;
		uint				_maxVertexAttribs;
		GLVertexAttribsInfo	_firstVA;
		GLVertexAttribsInfo	_secondVA;

		// Buffer //
		gles::GLuint		_vertexBuffer;
		gles::GLuint		_indexBuffer;
		buffer_stack_t		_vertexBufferStack;
		buffer_stack_t		_indexBufferStack;

		// Program //
		gles::GLuint		_program;

		// States //
		RenderState			_renderState;

		// Matrices //
		mat4				_model,
							_view,
							_projection,
							_cachedMV,
							_cachedVP,
							_cachedMVP;
		mat3				_cachedNormal;

		bool				_mvMatrixChanged,
							_vpMatrixChanged,
							_mvpMatrixChanged,
							_normMatrixChanged;

		// Viewport //
		viewport_stack_t	_viewportStack;

		// FrameBuffer //
		gles::GLuint		_framebuffer;

		color4f				_clearColor;
		float				_clearDepth;
		int					_clearStencil;

		// Debug //
		DEBUG_ONLY(
			GLVertexAttribsInfo	_programAttribs;
			GLVertexAttribsInfo	_attribs;
		)

	// methods
	public:
		StateManager (const EngineSubSystemsRef ess);
		~StateManager ();

		void Init ();
		void OnResized (const ivec2 &newSize);

		// Texture //
		void BindTexture (BindedTexture &tex, uint unit);
		void UnbindTexture (BindedTexture &tex);
		void ActiveTextureUnit (uint unit);
		bool IsTextureBinded (const BindedTexture &tex) const;
		uint GetActiveTextureUnit () const		{ return _activeTextureUnit; }

		// Vertex Attribs //
		void BindVertexArray (const GLVertexAttribs &vao, uint offset = 0, uint indexOffset = 0, gles::GLuint vb = 0);
		void BindVertexArray (const GLVertexAttribs &firstVAO, uint firstOffset, uint firstIndexOffset, gles::GLuint firstVB,
							  const GLVertexAttribs &secondVAO, uint secondOffset, uint secondIndexOffset, gles::GLuint secondVB);

		// Buffer //
		void BindIndexBuffer (gles::GLuint buf);
		void BindVertexBuffer (gles::GLuint buf);

		bool PushIndexBuffer (gles::GLuint buf);
		bool PopIndexBuffer ();

		bool PushVertexBuffer (gles::GLuint buf);
		bool PopVertexBuffer ();

		// Program //
		void UseProgram (gles::GLuint id);
		bool IsActiveProgram (gles::GLuint id);
		void SetProgramAttribs (const GLVertexAttribsInfo &attribs);

		// States //
		void SetColorState (const RenderState::ColorState &state);
		void SetColorState (const RenderState::ColorStateSeparate &state);
		void SetDepthState (const RenderState::DepthState &state);
		void SetStencilState (const RenderState::StencilState &state);
		void SetPolygonOffset (const RenderState::PolygonOffset &state);
		void SetCullMode (const RenderState::CullMode &state);
		void SetRenderState (const RenderState &state);

		void ResetStates ();

		void ColorMask (bool R, bool G, bool B, bool A);
		void Blend (bool enabled);
		void BlendColor (const color4u &color);
		void BlendColor (const color4f &color);

		void BlendFunc (gl_blend_func::type src, gl_blend_func::type dst);
		void BlendFunc (gl_blend_func::type srcRGB, gl_blend_func::type dstRGB,
						gl_blend_func::type srcA, gl_blend_func::type dstA);

		void BlendEquation (gl_blend_equation::type mode);
		void BlendEquation (gl_blend_equation::type modeRGB, gl_blend_equation::type modeA);

		void DepthFunc (gl_compare::type func);
		void DepthTest (bool enabled);
		void DepthWrite (bool enabled);

		void StencilOp (gl_stencil_op::type	sfail, gl_stencil_op::type dfail, gl_stencil_op::type dppass);
		void StencilFunc (gl_compare::type func, uint8 ref, uint8 mask);
		void StencilMask (uint8 mask);
		void StencilTest (bool enabled);
		
		// Draw //
		void DrawArrays (gl_primitive::type mode, uint first, uint count);
		void DrawElements (gl_primitive::type mode, uint count, gl_index::type type, const void *indices);

		// Matrices //
		void SetModelMatrix (const mat4 &model);
		void SetViewMatrix (const mat4 &view);
		void SetProjectionMatrix (const mat4 &proj);
		void SetModelViewProjectionMatrix (const mat4 &model, const mat4 &view, const mat4 &proj, const mat4 &mvp);

		const mat4 & GetModelMatrix ();
		const mat4 & GetViewMatrix ();
		const mat4 & GetProjectionMatrix ();
		const mat4 & GetModelViewMatrix ();
		const mat4 & GetViewProjectionMatrix ();
		const mat4 & GetModelViewProjectionMatrix ();
		const mat3 & GetNormalMatrix ();

		// Viewport //
		void Viewport (int x, int y, int width, int height);
		void Viewport (const ivec4 &viewport);
		void Viewport (const irect &viewport);
		
		void PushViewport (int x, int y, int width, int height);
		void PushViewport (const ivec4 &viewport);
		void PushViewport (const irect &viewport);
		void PushViewport ()						{ ivec4 vp = GetViewport();  return PushViewport( vp ); }

		void PopViewport ();
		
		const ivec4 & GetViewport () const			{ return _viewportStack.Get(); }
		const ivec4 & GetDefaultViewport () const	{ return _viewportStack.GetDefault(); }

		// FrameBuffer //
		void BindFrameBuffer (gles::GLuint id);
		gles::GLuint GetFrameBuffer () const;

		void Clear (bool color, bool depth, bool stencil);

		// clear with new value and return previosly value
		void ClearBuffer (const color4f &color, bool clearColor = true,
							float depth = 1.0f, bool clearDepth = false,
							int stencil = 0,	bool clearStencil = false);

		void ClearColor (const color4f &color);
		void ClearDepth (float value);
		void ClearStencil (int value);


	private:

		void _SetClearColor (const color4f &color);
		void _SetClearDepth (float value);
		void _SetClearStencil (int value);

		// Debug //
		void _CheckAttribs () const;
	};



	
/*
=================================================
	constructor
=================================================
*/
	inline StateManager::StateManager (const EngineSubSystemsRef ess) :
		BaseObject(ess),
		_maxTextureUnits(GLConst::MIN_TEXTURE_UNITS),
		_maxVertexAttribs(GLConst::MIN_VERTEX_ATTRIBS),
		_activeTextureUnit(0),
		_enabledVertexAtttribs(0), _program(0),
		_vertexBuffer(0), _indexBuffer(0),
		_mvMatrixChanged(false), _mvpMatrixChanged(false),
		_vpMatrixChanged(false), _normMatrixChanged(false),
		_framebuffer(0), _clearColor(255), _clearDepth(1.0f), _clearStencil(0)
	{
		ESS()->SetGLStateManager( this );

		_model = _view = _projection = _cachedMV = _cachedVP = _cachedMVP = mat4::Identity();
		_cachedNormal = mat3::Identity();
	}

/*
=================================================
	destructor
=================================================
*/
	inline StateManager::~StateManager ()
	{
		ESS()->SetGLStateManager( null );
	}
	
/*
=================================================
	Init
=================================================
*/
	inline void StateManager::Init ()
	{
		using namespace gles;

		_maxTextureUnits	= Min( C_GL_MAX_TEXTURE_IMAGE_UNITS, GLConst::MAX_TEXTURE_UNITS );
		_maxVertexAttribs	= Min( C_GL_MAX_VERTEX_ATTRIBS, GLConst::MAX_VERTEX_ATTRIBS );

		GL_CALL( glClearColor( _clearColor[0], _clearColor[1], _clearColor[2], _clearColor[3] ) );
		GL_CALL( glClearDepthf( _clearDepth ) );
		GL_CALL( glClearStencil( _clearStencil ) );

		ResetStates();

		ivec4 default_viewport;
		GL_CALL( glGetIntegerv( GL_VIEWPORT, default_viewport.ptr() ) );

		_viewportStack.SetDefault( default_viewport );
	}
	
/*
=================================================
	OnResized
=================================================
*/
	inline void StateManager::OnResized (const ivec2 &newSize)
	{
		ASSERT( _viewportStack.Empty() );
		_viewportStack.Clear();

		_viewportStack.SetDefault( ivec4( 0, 0, newSize.x, newSize.y ) );

		const ivec4 &	vp = _viewportStack.Get();

		using namespace gles;

		GL_CALL( glViewport( vp[0], vp[1], vp[2], vp[3] ) );
	}
	
/*
=================================================
	ActiveTextureUnit
=================================================
*/
	inline void StateManager::ActiveTextureUnit (uint unit)
	{
		using namespace gles;

		if ( _activeTextureUnit != unit )
		{
			GL_CALL( glActiveTexture( GL_TEXTURE0 + unit ) );
			_activeTextureUnit = unit;
		}
	}
	
/*
=================================================
	BindTexture
=================================================
*/
	inline void StateManager::BindTexture (BindedTexture &tex, uint unit)
	{
		using namespace gles;
		
		if ( tex._unit != -1 and tex._unit != unit )
			UnbindTexture( tex );
		
		ActiveTextureUnit( unit );

		BindedTexture * &	t = _textureUnits[ _activeTextureUnit ];
		BindedTexture *		p = &tex;

		// TODO: this condition is not safe
		if ( tex._unit == unit )
		{
			ASSERT( p == t );
			return;
		}

		tex._unit = unit;

		if ( t != p )
		{
			if ( t != null )
				t->_unit = -1;

			t = p;
		}

		GL_CALL( glBindTexture( tex.target, tex.id ) );
	}
	
/*
=================================================
	UnbindTexture
=================================================
*/
	inline void StateManager::UnbindTexture (BindedTexture &tex)
	{
		using namespace gles;

		const uint	unit = tex._unit;

		tex._unit = -1;

		if ( unit == -1 or _textureUnits[ unit ] == null )
		{
			WARNING( "Expected a not Null value!" );
			return;
		}

		ActiveTextureUnit( unit );
		
		_textureUnits[ unit ] = null;

		GL_CALL( glBindTexture( tex.target, 0 ) );
	}
	
/*
=================================================
	IsTextureBinded
=================================================
*/
	inline bool StateManager::IsTextureBinded (const BindedTexture &tex) const
	{
		if ( tex._unit == -1 )
			return false;

		if ( _textureUnits[ tex._unit ] == null )
			return false;

		if ( ComparePointers( _textureUnits[ tex._unit ], &tex ) )
			return true;

		WARNING( "!!!" );
		return false;
	}
	
/*
=================================================
	BindVertexArray
=================================================
*/
	inline void StateManager::BindVertexArray (const GLVertexAttribs &vao, uint offset, uint indexOffset, gles::GLuint vb)
	{
		using namespace gles;
		
		bitfield32_t	enabled_attribs;

		BindVertexBuffer( vb );

		FOR( i, vao )
		{
			GLVertexAttribs::GLAttrib const &	attr = vao.Get()[i];
			
			enabled_attribs.Set( attr.index + indexOffset, attr.enabled );

			if ( attr.enabled ) {
				GL_CALL( glVertexAttribPointer( attr.index + indexOffset, attr.count,
												attr.type, attr.norm, vao.Stride(),
												(const void *)(attr.offset + offset) ) );
			}
		}

		FOR( i, enabled_attribs )
		{
			bool old_val = _enabledVertexAtttribs.Get( i );
			bool new_val = enabled_attribs.Get( i );

			if ( new_val != old_val )
			{
				if ( new_val ) {
					GL_CALL( glEnableVertexAttribArray( i ) );
				} else {
					GL_CALL( glDisableVertexAttribArray( i ) );
				}
			}

			if ( i >= _maxVertexAttribs )
				break;
		}

		_enabledVertexAtttribs = enabled_attribs;

		DEBUG_ONLY(
			_attribs = GLVertexAttribsInfo( vao, indexOffset );
		)
	}
	
/*
=================================================
	BindVertexArray
=================================================
*/
	inline void StateManager::BindVertexArray (const GLVertexAttribs &firstVAO, uint firstOffset, uint firstIndexOffset, gles::GLuint firstVB,
												 const GLVertexAttribs &secondVAO, uint secondOffset, uint secondIndexOffset, gles::GLuint secondVB)
	{
		using namespace gles;
		
		bitfield32_t	enabled_attribs;

		BindVertexBuffer( firstVB );

		FOR( i, firstVAO )
		{
			GLVertexAttribs::GLAttrib const &	attr = firstVAO.Get()[i];
			
			enabled_attribs.Set( attr.index + firstIndexOffset, attr.enabled );

			if ( attr.enabled ) {
				GL_CALL( glVertexAttribPointer( attr.index + firstIndexOffset, attr.count,
												attr.type, attr.norm, firstVAO.Stride(),
												(const void *)(attr.offset + firstOffset) ) );
			}
		}

		BindVertexBuffer( secondVB );

		FOR( i, secondVAO )
		{
			GLVertexAttribs::GLAttrib const &	attr = secondVAO.Get()[i];
			
			ASSERT( not ( enabled_attribs.Get( attr.index + secondIndexOffset ) and attr.enabled ) and
					"Warning: overrided attrib from first vertex buffer" );

			enabled_attribs.Set( attr.index + secondIndexOffset, attr.enabled );

			if ( attr.enabled ) {
				GL_CALL( glVertexAttribPointer( attr.index + secondIndexOffset, attr.count,
												attr.type, attr.norm, secondVAO.Stride(),
												(const void *)(attr.offset + secondOffset) ) );
			}
		}

		FOR( i, enabled_attribs )
		{
			bool old_val = _enabledVertexAtttribs.Get( i );
			bool new_val = enabled_attribs.Get( i );

			if ( new_val != old_val )
			{
				if ( new_val ) {
					GL_CALL( glEnableVertexAttribArray( i ) );
				} else {
					GL_CALL( glDisableVertexAttribArray( i ) );
				}
			}

			if ( i >= _maxVertexAttribs )
				break;
		}

		_enabledVertexAtttribs = enabled_attribs;
		
		DEBUG_ONLY(
			_attribs = GLVertexAttribsInfo( firstVAO, firstIndexOffset,
											secondVAO, secondIndexOffset );
		)
	}
	
/*
=================================================
	BindIndexBuffer
=================================================
*/
	inline void StateManager::BindIndexBuffer (gles::GLuint buf)
	{
		using namespace gles;

		if ( _indexBuffer == buf )
			return;

		GL_CALL( glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, _indexBuffer = buf ) );
	}
	
/*
=================================================
	BindVertexBuffer
=================================================
*/
	inline void StateManager::BindVertexBuffer (gles::GLuint buf)
	{
		using namespace gles;

		if ( _vertexBuffer == buf )
			return;

		GL_CALL( glBindBuffer( GL_ARRAY_BUFFER, _vertexBuffer = buf ) );
	}
	
/*
=================================================
	PushIndexBuffer
=================================================
*/
	inline bool StateManager::PushIndexBuffer (gles::GLuint buf)
	{
		bool	res = _indexBufferStack.Push( buf );
		BindIndexBuffer( _indexBufferStack.Get() );
		return res;
	}
	
/*
=================================================
	PopIndexBuffer
=================================================
*/
	inline bool StateManager::PopIndexBuffer ()
	{
		bool	res = _indexBufferStack.Pop();
		BindIndexBuffer( _indexBufferStack.Get() );
		return res;
	}
	
/*
=================================================
	PushVertexBuffer
=================================================
*/
	inline bool StateManager::PushVertexBuffer (gles::GLuint buf)
	{
		bool	res = _vertexBufferStack.Push( buf );
		BindVertexBuffer( _vertexBufferStack.Get() );
		return res;
	}
	
/*
=================================================
	PopVertexBuffer
=================================================
*/
	inline bool StateManager::PopVertexBuffer ()
	{
		bool	res = _vertexBufferStack.Pop();
		BindVertexBuffer( _vertexBufferStack.Get() );
		return res;
	}
	
/*
=================================================
	UseProgram
=================================================
*/
	inline void StateManager::UseProgram (gles::GLuint id)
	{
		using namespace gles;

		if ( _program == id )
			return;

		GL_CALL( glUseProgram( _program = id ) );
	}
	
/*
=================================================
	UseProgram
=================================================
*/
	inline bool StateManager::IsActiveProgram (gles::GLuint id)
	{
		return _program == id;
	}

/*
=================================================
	SetProgramAttribs
=================================================
*/
	inline void StateManager::SetProgramAttribs (const GLVertexAttribsInfo &attribs)
	{
		DEBUG_ONLY(
			_programAttribs = attribs;
		)
	}
	
/*
=================================================
	SetColorState
=================================================
*/
	inline void StateManager::SetColorState (const RenderState::ColorState &s)
	{
		ColorMask( s.maskR, s.maskG, s.maskB, s.maskA );
		Blend( s.blend );

		if ( s.blend )
		{
			BlendColor( s.blendColor );
			BlendFunc( s.blendFuncSrc, s.blendFuncDst );
			BlendEquation( s.blendMode );
		}
	}
	
/*
=================================================
	SetColorState
=================================================
*/
	inline void StateManager::SetColorState (const RenderState::ColorStateSeparate &s)
	{
		ColorMask( s.maskR, s.maskG, s.maskB, s.maskA );
		Blend( s.blend );

		if ( s.blend )
		{
			BlendColor( s.blendColor );
			BlendFunc( s.blendFuncSrcRGB, s.blendFuncDstRGB, s.blendFuncSrcA, s.blendFuncDstA );
			BlendEquation( s.blendModeRGB, s.blendModeA );
		}
	}
	
/*
=================================================
	SetDepthState
=================================================
*/
	inline void StateManager::SetDepthState (const RenderState::DepthState &s)
	{
		DepthFunc( s.func );
		DepthWrite( s.write );
		DepthTest( s.test );
	}
	
/*
=================================================
	SetStencilState
=================================================
*/
	inline void StateManager::SetStencilState (const RenderState::StencilState &s)
	{
		StencilFunc( s.func, s.funcRef, s.funcMask );
		StencilOp( s.sfail, s.dfail, s.dppass );
		StencilMask( s.mask );
		StencilTest( s.test );
	}
	
/*
=================================================
	SetPolygonOffset
=================================================
*/
	inline void StateManager::SetPolygonOffset (const RenderState::PolygonOffset &s)
	{
		using namespace gles;

		RenderState::PolygonOffset & polygonOffset = _renderState.polygonOffset;

		if ( polygonOffset.factor != s.factor or
			 polygonOffset.units != s.units )
		{
			GL_CALL( glPolygonOffset( polygonOffset.factor = s.factor, polygonOffset.units = s.units ) );
		}

		if ( polygonOffset.enabled != s.enabled )
		{
			if ( polygonOffset.enabled = s.enabled ) {
				GL_CALL( glEnable( GL_POLYGON_OFFSET_FILL ) );
			} else {
				GL_CALL( glDisable( GL_POLYGON_OFFSET_FILL ) );
			}
		}
	}

/*
=================================================
	SetCullMode
=================================================
*/
	inline void StateManager::SetCullMode (const RenderState::CullMode &s)
	{
		using namespace gles;

		RenderState::CullMode & cullMode = _renderState.cullMode;

		if ( cullMode.enabled != s.enabled )
		{
			if ( cullMode.enabled = s.enabled ) {
				GL_CALL( glEnable( GL_CULL_FACE ) );
			} else {
				GL_CALL( glDisable( GL_CULL_FACE ) );
				return;
			}
		}

		if ( cullMode.cullBackFace != s.cullBackFace )
		{
			if ( cullMode.cullBackFace = s.cullBackFace ) {
				GL_CALL( glCullFace( GL_BACK ) );
			} else {
				GL_CALL( glCullFace( GL_FRONT ) );
			}
		}

		if ( cullMode.frontFaceCCW != s.frontFaceCCW )
		{
			if ( cullMode.frontFaceCCW = s.frontFaceCCW ) {
				GL_CALL( glFrontFace( GL_CCW ) );
			} else {
				GL_CALL( glFrontFace( GL_CW ) );
			}
		}
	}
	
/*
=================================================
	SetRenderState
=================================================
*/
	inline void StateManager::SetRenderState (const RenderState &s)
	{
		SetCullMode( s.cullMode );
		SetColorState( s.colorState );
		SetDepthState( s.depthState );
		SetStencilState( s.stencilState );
		SetPolygonOffset( s.polygonOffset );
	}
	
/*
=================================================
	ResetStates
=================================================
*/
	inline void StateManager::ResetStates ()
	{
		RenderState state = _renderState;

		_renderState.Invalidate();
		SetRenderState( state );
	}
	
/*
=================================================
	ColorMask
=================================================
*/
	inline void StateManager::ColorMask (bool R, bool G, bool B, bool A)
	{
		using namespace gles;

		RenderState::ColorStateSeparate & color_state = _renderState.colorState;

		if ( color_state.maskR == R and
			 color_state.maskG == G and
			 color_state.maskB == B and
			 color_state.maskA == A )
		{
			return;
		}

		GL_CALL( glColorMask( color_state.maskR = R, color_state.maskG = G,
							  color_state.maskB = B, color_state.maskA = A ) );
	}
	
/*
=================================================
	Blend
=================================================
*/
	inline void StateManager::Blend (bool enabled)
	{
		using namespace gles;

		RenderState::ColorStateSeparate & color_state = _renderState.colorState;

		if ( color_state.blend == enabled )
			return;

		if ( color_state.blend = enabled ) {
			GL_CALL( glEnable( GL_BLEND ) );
		} else {
			GL_CALL( glDisable( GL_BLEND ) );
		}
	}
	
/*
=================================================
	BlendColor
=================================================
*/
	inline void StateManager::BlendColor (const color4u &color)
	{
		using namespace gles;

		RenderState::ColorStateSeparate &	color_state = _renderState.colorState;

		if ( All( color_state.blendColor == color ) )
			return;

		color_state.blendColor = color;

		const color4f	c = color4f( color );

		GL_CALL( glBlendColor( c[0], c[1], c[2], c[3] ) );
	}
	
/*
=================================================
	BlendColor
=================================================
*/
	inline void StateManager::BlendColor (const color4f &color)
	{
		return BlendColor( color4u( color ) );
	}
	
/*
=================================================
	BlendFunc
=================================================
*/
	inline void StateManager::BlendFunc (gl_blend_func::type src, gl_blend_func::type dst)
	{
		using namespace gles;

		RenderState::ColorStateSeparate & color_state = _renderState.colorState;

		if ( color_state.blendFuncSrcRGB == src and
			 color_state.blendFuncSrcA   == src and
			 color_state.blendFuncDstRGB == dst and
			 color_state.blendFuncDstA   == dst )
		{
			return;
		}

		GL_CALL( glBlendFunc( color_state.blendFuncSrcRGB = color_state.blendFuncSrcA = src,
							  color_state.blendFuncDstRGB = color_state.blendFuncDstA = dst ) );
	}
	
/*
=================================================
	BlendFunc
=================================================
*/
	inline void StateManager::BlendFunc (gl_blend_func::type srcRGB, gl_blend_func::type dstRGB,
											gl_blend_func::type srcA, gl_blend_func::type dstA)
	{
		using namespace gles;

		RenderState::ColorStateSeparate & color_state = _renderState.colorState;

		if ( color_state.blendFuncSrcRGB == srcRGB and
			 color_state.blendFuncSrcA   == srcA   and
			 color_state.blendFuncDstRGB == dstRGB and
			 color_state.blendFuncDstA   == dstA )
		{
			return;
		}

		GL_CALL( glBlendFuncSeparate( color_state.blendFuncSrcRGB = srcRGB, color_state.blendFuncDstRGB = dstRGB,
									  color_state.blendFuncSrcA = srcA, color_state.blendFuncDstA = dstA ) );
	}
	
/*
=================================================
	BlendEquation
=================================================
*/
	inline void StateManager::BlendEquation (gl_blend_equation::type mode)
	{
		using namespace gles;

		RenderState::ColorStateSeparate & color_state = _renderState.colorState;

		if ( color_state.blendModeRGB == mode and
			 color_state.blendModeA   == mode )
		{
			return;
		}

		GL_CALL( glBlendEquation( color_state.blendModeRGB = color_state.blendModeA = mode ) );
	}
	
/*
=================================================
	BlendEquation
=================================================
*/
	inline void StateManager::BlendEquation (gl_blend_equation::type modeRGB, gl_blend_equation::type modeA)
	{
		using namespace gles;

		RenderState::ColorStateSeparate & color_state = _renderState.colorState;

		if ( color_state.blendModeRGB == modeRGB and
			 color_state.blendModeA   == modeA )
		{
			return;
		}

		GL_CALL( glBlendEquationSeparate( color_state.blendModeRGB = modeRGB, color_state.blendModeA = modeA ) );
	}
	
/*
=================================================
	DepthFunc
=================================================
*/
	inline void StateManager::DepthFunc (gl_compare::type func)
	{
		using namespace gles;

		RenderState::DepthState & depthState = _renderState.depthState;

		if ( depthState.func == func )
			return;

		GL_CALL( glDepthFunc( depthState.func = func ) );
	}
	
/*
=================================================
	DepthTest
=================================================
*/
	inline void StateManager::DepthTest (bool enabled)
	{
		using namespace gles;

		RenderState::DepthState & depthState = _renderState.depthState;

		if ( depthState.test == enabled )
			return;

		if ( depthState.test = enabled ) {
			GL_CALL( glEnable( GL_DEPTH_TEST ) );
		} else {
			GL_CALL( glDisable( GL_DEPTH_TEST ) );
		}
	}
	
/*
=================================================
	DepthWrite
=================================================
*/
	inline void StateManager::DepthWrite (bool enabled)
	{
		using namespace gles;

		RenderState::DepthState & depthState = _renderState.depthState;

		if ( depthState.write == enabled )
			return;

		GL_CALL( glDepthMask( depthState.write = enabled ) );
	}
	
/*
=================================================
	StencilOp
=================================================
*/
	inline void StateManager::StencilOp (gl_stencil_op::type sfail, gl_stencil_op::type dfail, gl_stencil_op::type dppass)
	{
		using namespace gles;

		RenderState::StencilState & stencilState = _renderState.stencilState;

		if ( stencilState.sfail  == sfail and
			 stencilState.dfail  == dfail and
			 stencilState.dppass == dppass )
		{
			return;
		}
		
		GL_CALL( glStencilOp( stencilState.sfail = sfail, stencilState.dfail = dfail, stencilState.dppass = dppass ) );
	}
	
/*
=================================================
	StencilFunc
=================================================
*/
	inline void StateManager::StencilFunc (gl_compare::type func, uint8 ref, uint8 mask)
	{
		using namespace gles;

		RenderState::StencilState & stencilState = _renderState.stencilState;

		if ( stencilState.func == func and
			 stencilState.funcRef == ref and
			 stencilState.funcMask == mask )
		{
			return;
		}
		
		GL_CALL( glStencilFunc( stencilState.func = func, stencilState.funcRef = ref, stencilState.funcMask = mask ) );
	}
	
/*
=================================================
	StencilMask
=================================================
*/
	inline void StateManager::StencilMask (uint8 mask)
	{
		using namespace gles;

		RenderState::StencilState & stencilState = _renderState.stencilState;

		if ( stencilState.mask == mask )
			return;

		GL_CALL( glStencilMask( stencilState.mask = mask ) );
	}
	
/*
=================================================
	StencilTest
=================================================
*/
	inline void StateManager::StencilTest (bool enabled)
	{
		using namespace gles;

		RenderState::StencilState & stencilState = _renderState.stencilState;

		if ( stencilState.test == enabled )
			return;

		if ( stencilState.test = enabled ) {
			GL_CALL( glEnable( GL_STENCIL_TEST ) );
		} else {
			GL_CALL( glDisable( GL_STENCIL_TEST ) );
		}
	}
	
/*
=================================================
	DrawArrays
=================================================
*/
	inline void StateManager::DrawArrays (gl_primitive::type mode, uint first, uint count)
	{
		using namespace gles;

		_CheckAttribs();
		GL_CALL( glDrawArrays( mode, first, count ) );
	}
	
/*
=================================================
	DrawElements
=================================================
*/
	inline void StateManager::DrawElements (gl_primitive::type mode, uint count, gl_index::type type, const void *indices)
	{
		using namespace gles;
		
		_CheckAttribs();
		GL_CALL( glDrawElements( mode, count, type, indices ) );
	}
	
/*
=================================================
	SetModelMatrix
=================================================
*/
	inline void StateManager::SetModelMatrix (const mat4 &model)
	{
		_model = model;
		_mvMatrixChanged = _mvpMatrixChanged = _normMatrixChanged = true;
	}
	
/*
=================================================
	SetViewMatrix
=================================================
*/
	inline void StateManager::SetViewMatrix (const mat4 &view)
	{
		_view = view;
		_mvMatrixChanged = _mvpMatrixChanged = _vpMatrixChanged = _normMatrixChanged = true;
	}
	
/*
=================================================
	SetProjectionMatrix
=================================================
*/
	inline void StateManager::SetProjectionMatrix (const mat4 &proj)
	{
		_projection = proj;
		_vpMatrixChanged = _mvpMatrixChanged = true;
	}
	
/*
=================================================
	SetModelViewProjectionMatrix
=================================================
*/
	inline void StateManager::SetModelViewProjectionMatrix (const mat4 &model, const mat4 &view, const mat4 &proj, const mat4 &mvp)
	{
		SetModelMatrix( model );
		SetViewMatrix( view );
		SetProjectionMatrix( proj );

		_mvMatrixChanged = false;
		_cachedMVP = mvp;
	}
	
/*
=================================================
	GetModelMatrix
=================================================
*/
	inline const mat4 & StateManager::GetModelMatrix ()
	{
		return _model;
	}
	
/*
=================================================
	GetViewMatrix
=================================================
*/
	inline const mat4 & StateManager::GetViewMatrix ()
	{
		return _view;
	}
	
/*
=================================================
	GetProjectionMatrix
=================================================
*/
	inline const mat4 & StateManager::GetProjectionMatrix ()
	{
		return _projection;
	}
	
/*
=================================================
	GetModelViewMatrix
=================================================
*/
	inline const mat4 & StateManager::GetModelViewMatrix ()
	{
		if ( _mvMatrixChanged )
		{
			_mvMatrixChanged = false;
			_cachedMV = _view * _model;
		}
		return _cachedMV;
	}
	
/*
=================================================
	GetViewProjectionMatrix
=================================================
*/
	inline const mat4 & StateManager::GetViewProjectionMatrix ()
	{
		if ( _vpMatrixChanged )
		{
			_vpMatrixChanged = false;
			_cachedVP = _projection * _view;
		}
		return _cachedVP;
	}
	
/*
=================================================
	GetModelViewProjectionMatrix
=================================================
*/
	inline const mat4 & StateManager::GetModelViewProjectionMatrix ()
	{
		if ( _mvpMatrixChanged )
		{
			_mvpMatrixChanged = false;
			_cachedMVP = GetViewProjectionMatrix() * _model;
		}
		return _cachedMVP;
	}
	
/*
=================================================
	GetNormalMatrix
=================================================
*/
	inline const mat3 & StateManager::GetNormalMatrix ()
	{
		if ( _normMatrixChanged )
		{
			_normMatrixChanged = false;
			_cachedNormal = GetModelViewMatrix().To<mat3>().Inverse().Transpose();
		}
		return _cachedNormal;
	}
	
/*
=================================================
	Viewport
=================================================
*/
	inline void StateManager::Viewport (int x, int y, int width, int height)
	{
		return Viewport( ivec4( x, y, width, height ) );
	}
	
/*
=================================================
	Viewport
=================================================
*/
	inline void StateManager::Viewport (const ivec4 &vp)
	{
		using namespace gles;

		if ( All( vp == _viewportStack.Get() ) )
			return;

		_viewportStack.Set(vp);
		GL_CALL( glViewport( vp.x, vp.y, vp.z, vp.w ) );
	}
	
/*
=================================================
	Viewport
=================================================
*/
	inline void StateManager::Viewport (const irect &vp)
	{
		return Viewport( vp.left, vp.bottom, vp.right - vp.left, vp.top - vp.bottom );
	}
	
/*
=================================================
	PushViewport
=================================================
*/
	inline void StateManager::PushViewport (int x, int y, int width, int height)
	{
		return PushViewport( ivec4( x, y, width, height ) );
	}
	
/*
=================================================
	PushViewport
=================================================
*/
	inline void StateManager::PushViewport (const ivec4 &vp)
	{
		using namespace gles;
		
		if ( Any( vp != _viewportStack.Get() ) )
		{
			GL_CALL( glViewport( vp.x, vp.y, vp.z, vp.w ) );
		}

		_viewportStack.Push(vp);
	}
	
/*
=================================================
	PushViewport
=================================================
*/
	inline void StateManager::PushViewport (const irect &vp)
	{
		return PushViewport( ivec4( vp.left, vp.bottom, vp.right - vp.left, vp.top - vp.bottom ) );
	}
	
/*
=================================================
	PopViewport
=================================================
*/
	inline void StateManager::PopViewport ()
	{
		using namespace gles;

		_viewportStack.Pop();
		
		const ivec4 & vp = _viewportStack.Get();

		GL_CALL( glViewport( vp.x, vp.y, vp.z, vp.w ) );
	}
	
/*
=================================================
	BindFrameBuffer
=================================================
*/
	inline void StateManager::BindFrameBuffer (gles::GLuint id)
	{
		using namespace gles;

		if ( id == _framebuffer )
			return;

		GL_CALL( glBindFramebuffer( GL_FRAMEBUFFER, _framebuffer = id ) );
	}
	
/*
=================================================
	GetFrameBuffer
=================================================
*/
	inline gles::GLuint StateManager::GetFrameBuffer () const
	{
		return _framebuffer;
	}
	
/*
=================================================
	Clear
=================================================
*/
	inline void StateManager::Clear (bool color, bool depth, bool stencil)
	{
		using namespace gles;

		GL_CALL( glClear( (int(color) << 14) | (int(depth) << 8) | (int(stencil) << 10) ) );
	}
	
/*
=================================================
	_SetClearColor
=================================================
*/
	inline void StateManager::_SetClearColor (const color4f &color)
	{
		using namespace gles;

		if ( All( color == _clearColor ) )
			return;

		GL_CALL( glClearColor( color[0], color[1], color[2], color[3] ) );
		_clearColor = color;
	}
	
/*
=================================================
	_SetClearDepth
=================================================
*/
	inline void StateManager::_SetClearDepth (float value)
	{
		using namespace gles;

		if ( value == _clearDepth )
			return;

		GL_CALL( glClearDepthf( _clearDepth = value ) );
	}
	
/*
=================================================
	_SetClearStencil
=================================================
*/
	inline void StateManager::_SetClearStencil (int value)
	{
		using namespace gles;

		if ( value == _clearStencil )
			return;

		GL_CALL( glClearStencil( _clearStencil = value ) );
	}
	
/*
=================================================
	ClearBuffer
=================================================
*/
	inline void StateManager::ClearBuffer (const color4f &color, bool clearColor,
											float depth,		 bool clearDepth,
											int stencil,		 bool clearStencil)
	{
		if ( clearColor )	_SetClearColor( color );
		if ( clearDepth )	_SetClearDepth( depth );
		if ( clearStencil )	_SetClearStencil( stencil );

		Clear( clearColor, clearDepth, clearStencil );
	}
	
/*
=================================================
	ClearColor
=================================================
*/
	inline void StateManager::ClearColor (const color4f &color)
	{
		_SetClearColor( color );
		Clear( true, false, false );
	}
	
/*
=================================================
	ClearDepth
=================================================
*/
	inline void StateManager::ClearDepth (float depth)
	{
		_SetClearDepth( depth );
		Clear( false, true, false );
	}
	
/*
=================================================
	ClearStencil
=================================================
*/
	inline void StateManager::ClearStencil (int stencil)
	{
		_SetClearStencil( stencil );
		Clear( false, false, true );
	}

/*
=================================================
	_CheckAttribs
=================================================
*/
	inline void StateManager::_CheckAttribs () const
	{
		//ASSERT( _programAttribs.Compare( _attribs ) and
		//		"Warning: program attribs not compatible with binded VAO" );
		DEBUG_ONLY(
			if ( not _programAttribs.Compare( _attribs ) )
			{
				string	s;
				s	<< "Warning: program attribs not compatible with binded VAO!\n"
					<< "program attribs:\n" << _programAttribs.ToString()
					<< "\ncurrent attribs:\n" << _attribs.ToString();
				WARNING( s.cstr() );
			}
		)
	}

}	// Graphics
}	// Engine